@im-nobsidian/core 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +137 -9
- package/dist/index.js +1621 -249
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -395,6 +395,13 @@ interface ProcessorMetadata {
|
|
|
395
395
|
readonly properties?: Record<string, unknown>;
|
|
396
396
|
readonly preserveMarkers?: PreserveMarker[];
|
|
397
397
|
readonly images?: ImageReference[];
|
|
398
|
+
/**
|
|
399
|
+
* 원시 Notion markdown export 가 압축형(펜스 밖 빈 줄 0)이었는지 — orchestrator 가
|
|
400
|
+
* `notionEnhancedToObsidian` **이전**에 판정해 전달한다. enhanced 변환의
|
|
401
|
+
* `<empty-block/>`→빈 줄 치환이 끝난 뒤에는 BlockSpacer 가 스스로 판정할 수 없다(D1).
|
|
402
|
+
* true: 무조건 재간격 / false: 무동작 / 미지정: 내용 기반 휴리스틱 폴백.
|
|
403
|
+
*/
|
|
404
|
+
readonly notionExportCompact?: boolean;
|
|
398
405
|
readonly [key: string]: unknown;
|
|
399
406
|
}
|
|
400
407
|
interface ConversionContext {
|
|
@@ -723,16 +730,33 @@ declare class NotionClient {
|
|
|
723
730
|
private fetchDatabaseModern;
|
|
724
731
|
getDatabaseTitle(databaseId: string): Promise<string>;
|
|
725
732
|
/**
|
|
726
|
-
* 발견된 DB 가 **동기화 가능한지**(접근 가능한 data source 가 있는지)
|
|
733
|
+
* 발견된 DB 가 **동기화 가능한지**(접근 가능한 data source 가 있는지) 판별한다.
|
|
727
734
|
* 신 모델(2025-09-03)에서 링크드 DB·미공유 데이터 소스·삭제 DB 는 `data_sources` 가
|
|
728
735
|
* 비어, 행 조회(`dataSources.query`)가 404 로 실패하고 그 전에 생성된 빈 폴더/`.base` 만
|
|
729
736
|
* 남긴다. 발견 단계에서 미리 걸러 **빈 폴더/.base 오염 + 매 pull 의 404 노이즈**를 차단한다.
|
|
730
737
|
* 제목은 함께 반환해 호출처가 추가 조회 없이 폴더명을 잡게 한다.
|
|
738
|
+
*
|
|
739
|
+
* F25: 원본이 같은 공유 범위에 있으면 linked view 컨테이너도 `data_sources` 가 **채워져**
|
|
740
|
+
* 온다(실측 — 워크스페이스 8건). 이때 data source 의 parent database 는 원본이므로,
|
|
741
|
+
* 첫 소스의 parent 가 자신이 아니면 `linkedOriginalDbId` 로 알려 컨테이너가 원본과 같은
|
|
742
|
+
* 행 집합을 이중 pull(폴더 릴레이 재배치 churn)하는 것을 발견 단계에서 차단한다.
|
|
731
743
|
*/
|
|
732
744
|
getDatabaseSyncability(databaseId: string): Promise<{
|
|
733
745
|
title: string;
|
|
734
746
|
queryable: boolean;
|
|
747
|
+
linkedOriginalDbId?: string;
|
|
735
748
|
}>;
|
|
749
|
+
/**
|
|
750
|
+
* linked database view 컨테이너를 원본 DB 로 해소한다.
|
|
751
|
+
* 컨테이너 자체는 `data_sources` 가 비어 행 조회가 불가능하지만(getDatabaseSyncability
|
|
752
|
+
* queryable=false), Views API 의 뷰 상세에는 원본 `data_source_id` 가 실리고 그 data source
|
|
753
|
+
* 의 parent 가 원본 database 다. 해소 실패(뷰 없음/권한 없음/자기참조)는 null — 호출처가
|
|
754
|
+
* 기존대로 접근 불가 처리한다.
|
|
755
|
+
*/
|
|
756
|
+
resolveLinkedDatabase(databaseId: string): Promise<{
|
|
757
|
+
originalDbId: string;
|
|
758
|
+
viewName: string;
|
|
759
|
+
} | null>;
|
|
736
760
|
getDatabaseSchema(databaseId: string): Promise<Record<string, {
|
|
737
761
|
id: string;
|
|
738
762
|
type: string;
|
|
@@ -797,7 +821,13 @@ declare class NotionClient {
|
|
|
797
821
|
fetchAllChildrenDeep(blockId: string): Promise<BlockObjectResponse[]>;
|
|
798
822
|
appendChildren(blockId: string, children: unknown[]): Promise<void>;
|
|
799
823
|
deleteBlock(blockId: string): Promise<void>;
|
|
824
|
+
/** single_part 업로드 상한 (Notion API 규격 20MB) */
|
|
825
|
+
private static readonly SINGLE_PART_MAX_BYTES;
|
|
826
|
+
/** multi_part 파트 크기 — 규격상 마지막 파트 제외 5~20MB, 10MB 고정 사용 */
|
|
827
|
+
private static readonly MULTI_PART_CHUNK_BYTES;
|
|
800
828
|
uploadFile(fileData: Blob, filename: string, contentType: string): Promise<string>;
|
|
829
|
+
/** 20MB 초과 파일의 multi_part 업로드 — part_number 는 API 규격상 문자열("1"부터) */
|
|
830
|
+
private uploadFileMultiPart;
|
|
801
831
|
search(params: {
|
|
802
832
|
query?: string;
|
|
803
833
|
filter?: {
|
|
@@ -1073,7 +1103,19 @@ declare class SyncOrchestrator {
|
|
|
1073
1103
|
private buildDiscoveredDbConfig;
|
|
1074
1104
|
/** 접근 불가 DB denylist 를 상태 메타에서 로드한다(하이픈 정규화). */
|
|
1075
1105
|
private loadInaccessibleDbIds;
|
|
1106
|
+
/** linked view 컨테이너 → 원본 DB 매핑을 상태 메타에서 로드한다(nohyph → nohyph). */
|
|
1107
|
+
private loadLinkedDbMap;
|
|
1076
1108
|
private pullDiscoveredDatabases;
|
|
1109
|
+
/**
|
|
1110
|
+
* pull 산출 md 의 인라인 DB placeholder(`**제목** *(Notion DB)*` + 보존 마커)를
|
|
1111
|
+
* `![[<실제 .base 경로>|제목]]` 임베드로 재작성한다(F22) — folder note 본문에서
|
|
1112
|
+
* DB 가 텍스트 한 줄이 아니라 실제 Bases 뷰로 보이게 하는 마지막 조각. linked view
|
|
1113
|
+
* 컨테이너는 linkedMap 을 거쳐 원본 DB 의 .base 로 향한다. .base 경로는 DatabaseSyncer
|
|
1114
|
+
* 가 실제 기록한 경로(baseFileInfo)가 SSOT — 폴더명과 .base 파일명은 새니타이즈 규칙이
|
|
1115
|
+
* 달라(공백→하이픈 vs 공백 보존) localFolder 로 추측하면 깨진 임베드가 된다(E2E 실측
|
|
1116
|
+
* 91/158건). 최종적으로 .base 실존까지 확인해, 없으면 placeholder 를 마커째 보존한다.
|
|
1117
|
+
*/
|
|
1118
|
+
private rewriteDbPlaceholderEmbeds;
|
|
1077
1119
|
private resolveNotionLinks;
|
|
1078
1120
|
private ensureDbSchema;
|
|
1079
1121
|
private pushCreate;
|
|
@@ -1134,6 +1176,13 @@ declare class SyncOrchestrator {
|
|
|
1134
1176
|
private pushUpdatePage;
|
|
1135
1177
|
private fetchPageMarkdown;
|
|
1136
1178
|
private collectInlineDbRefs;
|
|
1179
|
+
/**
|
|
1180
|
+
* 이번 라운드에 기록된 DB row md 를 읽어 본문의 인라인 DB 참조를 수집한다.
|
|
1181
|
+
* row 페이지는 DatabaseSyncer 자체 파이프라인으로 변환돼 fetchPageMarkdown 의
|
|
1182
|
+
* _inlineDbRefs 수집을 타지 않는다 — 디스크에 남은 보존 마커가 유일한 발견 신호다.
|
|
1183
|
+
* 부모 Notion 페이지 id 는 state DB 역조회(getByPath)로 얻는다(row upsert 직후라 항상 존재).
|
|
1184
|
+
*/
|
|
1185
|
+
private collectRowInlineRefs;
|
|
1137
1186
|
private resolveNotionIdWikilinks;
|
|
1138
1187
|
private extractParentId;
|
|
1139
1188
|
private resolveBlockToPageId;
|
|
@@ -1221,12 +1270,19 @@ declare class ImageHandler {
|
|
|
1221
1270
|
constructor(vaultFs: VaultFS, attachmentFolder?: string, notionClient?: NotionClient | undefined, customFetch?: typeof globalThis.fetch, options?: MediaOptions, stateDb?: IStateDB | undefined);
|
|
1222
1271
|
private get fetchFn();
|
|
1223
1272
|
downloadImage(url: string, pageTitle: string): Promise<ImageDownloadResult>;
|
|
1224
|
-
|
|
1273
|
+
private fetchImage;
|
|
1274
|
+
private persistImage;
|
|
1275
|
+
downloadAllImages(markdown: string, pageTitle: string, pageId?: string): Promise<{
|
|
1225
1276
|
content: string;
|
|
1226
1277
|
downloads: ImageDownloadResult[];
|
|
1227
1278
|
}>;
|
|
1228
|
-
uploadLocalImage(localPath: string): Promise<ImageUploadResult>;
|
|
1229
|
-
uploadAndAppendImages(pageId: string, images: ImageReference[]): Promise<ImageUploadResult[]>;
|
|
1279
|
+
uploadLocalImage(localPath: string, noteFolder?: string): Promise<ImageUploadResult>;
|
|
1280
|
+
uploadAndAppendImages(pageId: string, images: ImageReference[], notePath?: string): Promise<ImageUploadResult[]>;
|
|
1281
|
+
/**
|
|
1282
|
+
* 임베드 경로를 볼트 실제 파일로 해석한다(F23). Obsidian 위키링크 임베드는 노트 옆
|
|
1283
|
+
* 파일을 파일명만으로 참조하는 게 관례라, 노트 폴더 → 볼트 루트 → attachments/ 순으로
|
|
1284
|
+
* 시도한다. 폴백은 조용히 진행하고, 전부 실패했을 때만 마지막 오류를 던진다.
|
|
1285
|
+
*/
|
|
1230
1286
|
private readImageFromVault;
|
|
1231
1287
|
private getContentTypeFromPath;
|
|
1232
1288
|
downloadAllFiles(markdown: string, pageTitle: string): Promise<{
|
|
@@ -1237,6 +1293,16 @@ declare class ImageHandler {
|
|
|
1237
1293
|
private downloadFile;
|
|
1238
1294
|
private isNotionImageUrl;
|
|
1239
1295
|
private isNotionFileUrl;
|
|
1296
|
+
/**
|
|
1297
|
+
* notion-hosted 파일 URL 하나를 로컬 첨부로 로컬라이즈한다(P3-A).
|
|
1298
|
+
* DB 행의 `files` 속성처럼 본문 밖에 있는 서명 URL 용 — 서명 URL 은 약 1시간 뒤
|
|
1299
|
+
* 만료되어 frontmatter 에 남으면 깨진 링크가 되고, push 로 되밀면 Notion 원본이
|
|
1300
|
+
* external(만료 URL)로 오염된다(실측). notion-hosted 가 아닌 URL(사용자가 넣은
|
|
1301
|
+
* 진짜 외부 링크)은 다운로드 대상이 아니므로 null 을 반환한다.
|
|
1302
|
+
*
|
|
1303
|
+
* @returns 로컬 첨부 경로(`attachments/...`), 다운로드 대상이 아니거나 실패 시 null
|
|
1304
|
+
*/
|
|
1305
|
+
localizeNotionFileUrl(url: string, caption: string): Promise<string | null>;
|
|
1240
1306
|
private guessExtension;
|
|
1241
1307
|
private guessFileExtension;
|
|
1242
1308
|
}
|
|
@@ -1260,6 +1326,13 @@ declare class FileHandler {
|
|
|
1260
1326
|
private readonly sema;
|
|
1261
1327
|
constructor(vaultFs: VaultFS, notionClient: NotionClient, stateDb: IStateDB, concurrency?: number);
|
|
1262
1328
|
pushFilesForFolder(folderPageId: string, folderPath: string): Promise<FileUploadResult[]>;
|
|
1329
|
+
/**
|
|
1330
|
+
* 업로드 후보 목록 — 도구 내부 산출물(`.base`/`.notion.json`)은 제외한다.
|
|
1331
|
+
* node 측 VaultFS 는 과거 `.base` 를 워커에서 빼는 식으로 우회했지만, Obsidian
|
|
1332
|
+
* 어댑터는 모든 파일을 반환하므로 플러그인 push 가 사이드카를 Notion 첨부로
|
|
1333
|
+
* 오염시켰다. 소비자인 여기서 일괄 차단해 구현체 간 동작을 통일한다.
|
|
1334
|
+
*/
|
|
1335
|
+
private listUploadableFiles;
|
|
1263
1336
|
pushAllFiles(): Promise<FileUploadResult[]>;
|
|
1264
1337
|
downloadFileBlock(url: string, filename: string, targetFolder: string): Promise<FileDownloadResult>;
|
|
1265
1338
|
downloadFileBlocks(blocks: Array<{
|
|
@@ -1295,6 +1368,12 @@ interface DatabaseSyncResult {
|
|
|
1295
1368
|
* 엉뚱한 행을 골라 정작 바뀐 행의 relation UUID 를 영영 해소하지 못했다(M3). push 경로는 항상 빈 배열.
|
|
1296
1369
|
*/
|
|
1297
1370
|
writtenPaths: string[];
|
|
1371
|
+
/**
|
|
1372
|
+
* F25: 이 config 가 linked view 컨테이너로 판정된 경우 data source 원본 database id.
|
|
1373
|
+
* 행 처리는 원본 config 에 양보하고 건너뛰었으므로, 호출처(디스커버리 캐시)는 이 config
|
|
1374
|
+
* 를 제거하고 linked 매핑을 기록해야 다음 pull 부터 이중 방문 자체가 사라진다.
|
|
1375
|
+
*/
|
|
1376
|
+
linkedOriginalDbId?: string;
|
|
1298
1377
|
}
|
|
1299
1378
|
declare class DatabaseSyncer {
|
|
1300
1379
|
private readonly config;
|
|
@@ -1306,10 +1385,28 @@ declare class DatabaseSyncer {
|
|
|
1306
1385
|
private readonly propertyMapper;
|
|
1307
1386
|
private readonly baseFileGenerator;
|
|
1308
1387
|
private readonly sidecarGenerator;
|
|
1388
|
+
/**
|
|
1389
|
+
* 이번 프로세스가 실제로 기록한 .base 경로/DB 제목 (databaseId nohyph → info).
|
|
1390
|
+
* placeholder 임베드 재작성(F22)의 SSOT — 폴더명(하이픈 새니타이즈)과 .base 파일명
|
|
1391
|
+
* (sanitizeFileName: 공백·점 보존)은 규칙이 달라 localFolder 로 추측한 경로는 깨진
|
|
1392
|
+
* 임베드가 된다. generateBaseFile 은 매 pull 모든 DB 에 대해 실행되므로 pull 종료
|
|
1393
|
+
* 시점에는 성공한 DB 전체가 채워져 있다.
|
|
1394
|
+
*/
|
|
1395
|
+
readonly baseFileInfo: Map<string, {
|
|
1396
|
+
basePath: string;
|
|
1397
|
+
title: string;
|
|
1398
|
+
}>;
|
|
1309
1399
|
constructor(config: Config, stateDb: IStateDB, notionClient: NotionClient, vaultFs: VaultFS, pipeline: ConversionPipeline, imageHandler: ImageHandler);
|
|
1310
1400
|
pullAll(): Promise<DatabaseSyncResult>;
|
|
1311
1401
|
pushAll(): Promise<DatabaseSyncResult>;
|
|
1312
|
-
pullDatabase(dbConfig: DatabaseSyncConfig
|
|
1402
|
+
pullDatabase(dbConfig: DatabaseSyncConfig, opts?: {
|
|
1403
|
+
/**
|
|
1404
|
+
* F25: database id(하이픈 유무 무관) → 그 DB 행이 사는 로컬 폴더. 디스커버리 캐시가
|
|
1405
|
+
* 아는 DB 만 반환하고 모르면 null. linked view 컨테이너 판정 시 원본 폴더를 아는
|
|
1406
|
+
* 경우에만 행 소유를 양보한다 — 원본이 미발견이면 이 컨테이너가 유일한 접근 통로다.
|
|
1407
|
+
*/
|
|
1408
|
+
resolveDbFolder?: (databaseId: string) => string | null;
|
|
1409
|
+
}): Promise<DatabaseSyncResult>;
|
|
1313
1410
|
private pullDatabaseViews;
|
|
1314
1411
|
private generateBaseFile;
|
|
1315
1412
|
/**
|
|
@@ -1324,6 +1421,15 @@ declare class DatabaseSyncer {
|
|
|
1324
1421
|
* 결정적 직렬화라 동일 DB 상태 → 동일 바이트 → 멱등(drift/churn 0).
|
|
1325
1422
|
*/
|
|
1326
1423
|
private generateSidecar;
|
|
1424
|
+
/**
|
|
1425
|
+
* DB 행 `files` 속성의 notion-hosted 서명 URL 을 로컬 첨부로 로컬라이즈한다(P3-A).
|
|
1426
|
+
* 서명 URL 은 약 1시간 뒤 만료되어 frontmatter 에 남으면 깨진 링크가 되고, push 로
|
|
1427
|
+
* 되밀면 Notion 원본이 만료 URL 의 external 파일로 오염된다(실측). 다운로드 성공분은
|
|
1428
|
+
* `[[attachments/..]]` 위키링크로 대체한다 — Bases 카드 `image:` 는 위키링크 스칼라를
|
|
1429
|
+
* 렌더하므로 갤러리 커버도 유지된다. 원본 파일명은 raw 속성의 `name` 에서 취한다
|
|
1430
|
+
* (extractValue 는 URL 만 남긴다). 사용자가 넣은 진짜 외부 URL 은 그대로 둔다.
|
|
1431
|
+
*/
|
|
1432
|
+
private localizeFileProperties;
|
|
1327
1433
|
private pullDatabasePage;
|
|
1328
1434
|
private pushDatabase;
|
|
1329
1435
|
/**
|
|
@@ -1532,11 +1638,32 @@ declare class FrontmatterGenerator implements Processor {
|
|
|
1532
1638
|
process(input: ProcessorInput): ProcessorOutput;
|
|
1533
1639
|
}
|
|
1534
1640
|
|
|
1641
|
+
/**
|
|
1642
|
+
* 다른 복원기가 소비하지 못하고 실제로 유실된 보존 마커를 재삽입하는 최후 보루.
|
|
1643
|
+
*
|
|
1644
|
+
* 핵심 전제(F28 교훈): DB 에 저장된 startIndex 는 **push 시점 문자열**의 절대
|
|
1645
|
+
* 오프셋이라 pull 산출물에서는 무의미하다 — 프론트매터 앞·단어 한가운데에
|
|
1646
|
+
* 마커를 박아 파일을 파손시켰다. 재삽입은 반드시
|
|
1647
|
+
* 1) 이미 의미적으로 복원된 마커는 건너뛰고(위키링크 → `[[...]]` 존재 등),
|
|
1648
|
+
* 2) 텍스트 앵커(__anchor)를 1차 위치 수단으로 쓰며,
|
|
1649
|
+
* 3) 프론트매터 뒤·줄 경계에만, 마커 단독 줄로 삽입한다.
|
|
1650
|
+
*
|
|
1651
|
+
* 이중 모드: `__anchor` 키가 없는 마커는 구버전 DB blob(수집기가 앵커를 안 남기던
|
|
1652
|
+
* 시절)이다. 이들은 구계약 — "변환이 마커만 제거했다면 startIndex 오프셋 원문 삽입이
|
|
1653
|
+
* 원본을 정확히 재구성한다" — 을 유지하되 프론트매터 앞 삽입만 금지한다. 위해한
|
|
1654
|
+
* 레거시 사례(빈 params 껍데기, 이미 복원된 위키링크)는 render-null 폐기와 의미
|
|
1655
|
+
* 복원 검사가 이미 걸러낸다.
|
|
1656
|
+
*/
|
|
1535
1657
|
declare class PreserveMarkerInjector implements Processor {
|
|
1536
1658
|
readonly name = "PreserveMarkerInjector";
|
|
1537
1659
|
readonly order = 100;
|
|
1538
1660
|
process(input: ProcessorInput): ProcessorOutput;
|
|
1539
|
-
|
|
1661
|
+
/** 마커의 본문 표현. 복원 불가능한(빈) 마커는 null — 삽입하지 않고 폐기한다. */
|
|
1662
|
+
private renderMarker;
|
|
1663
|
+
/** 마커가 이미 원래 문법으로 복원돼 있으면 재삽입은 중복·오염이다. */
|
|
1664
|
+
private isSemanticallyRestored;
|
|
1665
|
+
/** 삽입 위치: 앵커 → startIndex(줄 경계 보정) 순. 항상 프론트매터 뒤. */
|
|
1666
|
+
private findInsertionPoint;
|
|
1540
1667
|
}
|
|
1541
1668
|
|
|
1542
1669
|
declare class LocalImageRestorer implements Processor {
|
|
@@ -1756,9 +1883,10 @@ type BasesViewType = "table" | "cards" | "list";
|
|
|
1756
1883
|
* Notion 뷰 타입 → Obsidian Bases 뷰 타입 매핑(SSOT).
|
|
1757
1884
|
*
|
|
1758
1885
|
* `null` = Bases 에 대응 뷰가 없어 `.base` 로 표현 불가. 이 경우 해당 뷰는 `.base` 에서
|
|
1759
|
-
* 누락되므로, 사이드카(`<db>.notion.json`)가 원본 뷰 설정을 무손실
|
|
1760
|
-
*
|
|
1761
|
-
*
|
|
1886
|
+
* 누락되므로, 사이드카(`<db>.notion.json`)가 원본 뷰 설정을 무손실 보존한다.
|
|
1887
|
+
* 뷰는 현재 pull-authoritative — Notion API 가 2026-03-19 뷰 생성/수정/삭제를 열었지만
|
|
1888
|
+
* 본 프로젝트는 아직 뷰 write-back 을 구현하지 않았다(로드맵 항목). push 로 되돌릴 수
|
|
1889
|
+
* 없는 동안 "조용한 유실 금지"는 곧 "보존 + 정직한 degrade 리포트"를 의미한다.
|
|
1762
1890
|
*/
|
|
1763
1891
|
declare const NOTION_TO_BASES_VIEW: Record<string, BasesViewType | null>;
|
|
1764
1892
|
/** Notion 뷰 타입의 Bases 대응 타입을 돌려준다. 미지원/미상 타입은 `null`. */
|